//Version 15 - Parte A y B:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <fcntl.h>
#define STDIN_FID 0
#define STDOUT_FID 1

int parsing(char line[], char* args[], char type1[], char type2[]);
void command(char* args[], char* path);
void seek(char *arreglo[]);
void changeDir(char* args[], char* path);
void showPrompt();
int whichstatus(char* args[]);
void redirection(char* file_name);
void searchDot(char *args[]);
void searchPath(char *args[]);
void pipee(char *args[]);

int cantargs;
char* directories[100];
char* PATH;
int flagstatus;
int position;

int main(int argc, char *argv[]){
    printf("--------------------------BAASH--------------------------\n");
    char* args[100];   
    char line[1024];
    
    PATH = getenv("PATH");
    parsing(PATH, directories, ":",":");
        
    while(1){  
        flagstatus=0;
        showPrompt();                  
		if (fgets(line, 1024, stdin) == NULL) return 0;     //Exit
		else{
			cantargs=parsing(line, args, " ", " \n");    	//Parsea la linea de entrada.
			position=whichstatus(args);
			command(args, getcwd(NULL, 0));     			//sino, interpreta el comando (agrs[0])
		}        
    }
    return 0;
}

/*
 * De acuerdo a lo ingreado, testea si se ingreso < > | ó & y cambia flagstatus
 * @param char* args[]
 * @return int
 */
int whichstatus(char* args[]){
        int i;
        for (i=0;i<cantargs;i++){
                if(strcmp(args[i],"&")==0){
                        args[i] = NULL;
                        cantargs--;
                        flagstatus=1;
                        return i;
                }
                if(strcmp(args[i],"<")==0){
                        args[i] = NULL;
                        cantargs--;
                        flagstatus=2;
                        return i;
                }
                if(strcmp(args[i],">")==0){
                        args[i] = NULL;
                        cantargs--;
                        flagstatus=3;
                        return i;
                }
                if(strcmp(args[i],"|")==0){
                        flagstatus=4;
                        return i;
                }
        }return i;
}

/*
 * Muestra el prompt
 * @param void
 * @return void
 */
void showPrompt(){
    printf("%s # %s > ", getenv("LOGNAME"),(char*) getcwd(NULL, 0));            
}

/*
 * Parsea una linea, de acuerdo a los parametros recibidos
 * @param char line
 * @param char* args[]
 * @param char type1[]
 * @param char type2[]
 * @return void
 */
int parsing(char line[], char* args[], char type1[], char type2[]){ 
    int i = 0;
    args[i] = strtok(line, type1);    
    while((args [i]) != NULL) {
                i++;
        args[i] = strtok (NULL, type2);
    }
    return i;
}

/*
 * Interpreta el comando: si es cd llama a changedir(), si no es cd llama a seek().
 * @param char* args[]
 * @param char* path
 * @return void
 */
void command(char* args[], char* path){
    if(strcmp(args[0], "exit\n") == 0) exit(1);
    if(strcmp(args[0], "\n") == 0) return;
    if(strcmp(args[0], "cd") == 0) changeDir(args, path);
        else seek(args);
}

/*
 * Cambia el directorio (comando cd)
 * @param char* args[]
 * @param char* path
 * @return void
 */
void changeDir(char* args[], char* path){
    if( chdir(args[1]) != 0 ) perror("Command not found\n");
        else path = (char*) getcwd(NULL, 0);
}

/*
 * Evaulua flagstatus, y manda a ejecutar el parametro recibido
 * @param char *args[]
 * @return void
 */
void seek(char *args[]){
    int flag = 0;
    pid_t pid;

    
    if (flagstatus==4) pipee(args);			//Si tiene | llama a pipee 
    else{
		if ( (pid = fork()) == 0){  // HIJO ---------------------------------------------------------------------------------------
                
			if(flagstatus==2||flagstatus==3){
				redirection(args[position+1]);
				args[position+1]=NULL;
			}
			if(args[0][0]=='.'){          	//Si comienza con un ./ o   ../
				searchDot(args);
			}
			else searchPath(args);			//Si no comienza con ./ o ../
		}
		else if ( pid>0 )flag =-1; // PADRE ---------------------------------------------------------------------------------------
                
		if(flagstatus!=1){                                     //Si no habia &, espera al hijo
			waitpid(pid, &flag, 0);         
			args=NULL;                      
		}
		else{                                                  //Si habia &, no lo espera
			args = NULL;
			waitpid(pid, &flag, WNOHANG);
		}
	}  
}

/*
 * Redireccion de I/O, si hay < ó >
 * @param char *filename
 * @return void
 */
void redirection(char* file_name){
        int fid;

        if(flagstatus ==2){
                fid = open(file_name, O_RDONLY, S_IWUSR|S_IRUSR);
                if(fid<0) perror("open");
                close(STDIN_FID);
                if(dup(fid)<0)perror("dup");
        }

        if(flagstatus ==3){
                fid = open(file_name, O_WRONLY|O_CREAT|O_TRUNC, S_IWUSR|S_IRUSR);
                if(fid<0) perror("open");
                close(STDOUT_FID);
                if(dup(fid)<0)perror("dup");
        }
}    
  
/*
 * Busca el parametro recibido en directorio actual o directorios superiores	
 * @param char *args[]
 * @return void
 */
void searchDot(char *args[]){
	char str[200];
	char * aux1;
    char * aux2;
    int status = 0;
    
    aux1 = strtok(args[0],"/");     
    aux2 = aux1;
    
    while((status == 0)&&((aux1 = strtok(NULL,"/"))!= NULL)){
		status = chdir(aux2);   
        aux2 = aux1;            
	}                                       
    
	char* path = (char*) getcwd(NULL, 0);   //Encuentra el pwd.
	strcat (path,"/");
	strcat (path,aux2);     
	strncpy(str,path,sizeof(str));
    
	//Quita el final de linea, solo si no habia & (ya que si habia, fue quitado):
	if (flagstatus==0){              
		int j=0;
        int n=0;
		while(str[j]!='\0')j++;                
		for(n=0;n<j;n++) if((str[n]=='\n')||(str[n]=='\0'))str[n]='\0';
	}
                        
	if((access(str,F_OK))==0){
		execv(str, args);
	}
	else{
		perror("ERROR, File not found \n");
		exit(1);
	}
} 

/*
 * Busca el parametro recibido en PATH	
 * @param char *args[]
 * @return void
 */
void searchPath(char *args[]){
	int num = 0;
	char str[200];
	int i = 0;        
	char* dir[100];
                
	while( directories[i] != NULL){                    
		//Busca en PATH el comando especificado:    
		//Arma el string = path + / + comando:    
		dir[i] = directories[i];
		strcpy (str, dir[i]);                            
		strcat (str, "/");
		strcat(str, args[0]);

		//Quita el final de linea, solo si no habia & (ya que si habia, fue quitado):
		if (flagstatus==0){
			int j = 0;
			while(str[j]!='\0')j++;                
			if ( args[1] != NULL ) str[j] = '\0';
            else str[j-1] = '\0';
		}
		
		//Si lo encuentra lo ejecuta:
		if( (access(str,F_OK)) == 0 ){
			execv(str, args);
			num = 1;
        }
		else i++;       //Si no lo encientra incrementa i, para buscar en otro directorio.
	}
             
	//Si no encontro el comando en todo PATH muestra un mensaje de error:
	if( num == 0 ){
		perror("ERROR, File not found ");
        exit(1);
	}
}

/*
 * Ejecuta dos procesos comunicados entre si (por haber |)	
 * @param char *args[]
 * @return void
 */
void pipee(char *args[]){
	
	char* args1[position];
	char* args2[cantargs-position];
	int i,j;
	int x=0;
	int fork_id;
	int fork_id2;
	int server[2];
	
	//Divido args[0] en dos: args1[] y args2[], separados por |
	for (i=0;i<position;i++){
			args1[i]=args[i];
			args1[i+1]=NULL;
	}
	for (j=i+1;j<cantargs;j++){
			args2[x]=args[j];
			args2[x+1]=NULL;
			x++;
	}
	
	//Crea el primer hijo, para que ejecute args1
	pipe(server);	
	fork_id = fork();
	if(fork_id == 0){
		close(1);
		dup(server[1]);
		close(server[0]); close(server[1]);
		if (args1[0][0]=='.'){
			searchDot(args1);
			}
		else searchPath(args1);
	}
	
	//Crea el segundo hijo, para que ejecute args2
	fork_id2=fork();
	if(fork_id2==0){	
		close(0);
		dup(server[0]);
		close(server[0]); 
		close(server[1]);
		if (args2[0][0]=='.'){
			searchDot(args2);
		}
		else searchPath(args2);
	}
	
	close (server[0]);
	close(server[1]);
	wait(&fork_id);
	wait(&fork_id2);
	
	if (fork_id < 0)printf("error, proceso1");
	if (fork_id2 < 0)printf("error, proceso2");
}
